﻿using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Reflection;
using JiaBao.Core.Domain;
using JiaBao.Data.Mapping;

namespace JiaBao.Data
{
    public class JiaBaoObjectContext : DbContext, IDbContext
    {
        #region Fields

        #endregion

        #region 属性

        /// <summary>
        /// 是否启用了代理创建
        /// </summary>
        public bool ProxyCreationEnabled
        {
            get => Configuration.ProxyCreationEnabled;
            set => Configuration.ProxyCreationEnabled = value;
        }

        /// <summary>
        /// 是否启用自动检测更改
        /// </summary>
        public bool AutoDetectChangesEnabled
        {
            get => Configuration.AutoDetectChangesEnabled;
            set => Configuration.AutoDetectChangesEnabled = value;
        }

        #endregion

        #region Ctor
        public JiaBaoObjectContext() : base("NopDb")

        {

        }
        public JiaBaoObjectContext(string nameOrConnectionStirng)
            : base(nameOrConnectionStirng) { }

        #endregion

        /// <summary>
        /// 生成数据库脚本
        /// </summary>
        /// <returns></returns>
        public string CreateDatabaseScript()
        {
            return ((IObjectContextAdapter)this).ObjectContext.CreateDatabaseScript();
        }

        /// <summary>
        /// 分离实体
        /// </summary>
        /// <param name="entity"></param>
        public void Detach(object entity)
        {
            if (entity == null)
                throw new ArgumentNullException("entity");
            ((IObjectContextAdapter)this).ObjectContext.Detach(entity);
        }

        /// <summary>
        /// 执行sql语句返回受影响行数
        /// </summary>
        /// <param name="sql"></param>
        /// <param name="doNotEnsureTransaction"></param>
        /// <param name="timeout"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public int ExecuteSqlCommand(string sql, bool doNotEnsureTransaction, int? timeout = null, params object[] parameters)
        {
            int? previousTimout = null;
            if (timeout.HasValue)
            {
                previousTimout = ((IObjectContextAdapter)this).ObjectContext.CommandTimeout;
                ((IObjectContextAdapter)this).ObjectContext.CommandTimeout = timeout;
            }
            var trnsactionalBehavior = doNotEnsureTransaction
                ? TransactionalBehavior.DoNotEnsureTransaction
                : TransactionalBehavior.EnsureTransaction;

            var result = Database.ExecuteSqlCommand(trnsactionalBehavior, sql, parameters);

            if (timeout.HasValue)
                ((IObjectContextAdapter)this).ObjectContext.CommandTimeout = previousTimout;

            return result;

        }

        /// <summary>
        /// 执行存储过程并返回实体结果
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="commandText"></param>
        /// <param name="paraeters"></param>
        /// <returns></returns>
        public IList<TEntity> ExecuteStoredProcedureList<TEntity>(string commandText, params object[] paraeters) where TEntity : BaseEntity, new()
        {
            if (paraeters != null && paraeters.Length > 0)
            {
                for (int i = 0; i <= paraeters.Length; i++)
                {
                    var p = paraeters[i] as DbParameter;
                    if (p == null)
                        throw new Exception("参数错误");

                    commandText += i == 0 ? " " : ", ";
                    commandText += "@" + p.ParameterName;
                    if (p.Direction == System.Data.ParameterDirection.InputOutput || p.Direction == System.Data.ParameterDirection.Output)
                        commandText += " outPut";
                }
            }
            var result = Database.SqlQuery<TEntity>(commandText, paraeters).ToList();

            bool acd = Configuration.AutoDetectChangesEnabled;
            try
            {
                Configuration.AutoDetectChangesEnabled = false;
                for (int i = 0; i < result.Count - 1; i++)
                    result[i] = AttachEntityToContext(result[i]);
            }
            finally
            {
                Configuration.AutoDetectChangesEnabled = acd;
            }

            return result;

        }

        /// <summary>
        /// 执行sql语句返回泛型结果集
        /// </summary>
        /// <typeparam name="TElement"></typeparam>
        /// <param name="sql"></param>
        /// <param name="paramenters"></param>
        /// <returns></returns>
        public IEnumerable<TElement> SqlQuery<TElement>(string sql, params object[] paramenters)
        {
            return Database.SqlQuery<TElement>(sql, paramenters);
        }

        /// <summary>
        /// 获取DBSet
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <returns></returns>
        IDbSet<TEntity> IDbContext.Set<TEntity>()
        {
            return base.Set<TEntity>();
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            var typesToRegister = Assembly.GetExecutingAssembly().GetTypes()
                                    .Where(type => !string.IsNullOrEmpty(type.Namespace))
                                    .Where(type => type.BaseType != null && type.BaseType.IsGenericType && type.BaseType.GetGenericTypeDefinition() == typeof(JiaBaoEntityTypeConfiguration<>));

            foreach (var type in typesToRegister)
            {
                dynamic configurationInstance = Activator.CreateInstance(type);
                modelBuilder.Configurations.Add(configurationInstance);
            }

            base.OnModelCreating(modelBuilder);
        }

        #region 内部方法

        /// <summary>
        /// 将实体附加到上下文或返回已附加的实体
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="entity"></param>
        /// <returns></returns>
        protected virtual TEntity AttachEntityToContext<TEntity>(TEntity entity) where TEntity : BaseEntity, new()
        {
            var alreadyAttached = Set<TEntity>().Local.FirstOrDefault(x => x.Id == entity.Id);
            if (alreadyAttached == null)
            {
                Set<TEntity>().Attach(entity);
                return entity;
            }
            return alreadyAttached;
        }
        #endregion
    }
}
